package com.umeox.moto.common.recorder;

import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.text.TextUtils;

import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class PlayAudioTask implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, Runnable {
    private final Object mLock = new Object();

    private final Executor mainThread;
    private final ThreadPoolExecutor playThread;
    private Future playFuture;

    private Context context;
    private AudioPlayCallback callback;

    private MediaPlayer mPlayer;
    private volatile PlayerState mPlayState;
    private String dataSource;

    private PowerManager.WakeLock mWakeLock;
    private AudioManager mAudioManager;

    public PlayAudioTask(Context context) {
        mainThread = new MainThreadExecutor();
        playThread = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadFactory() {
                    private final AtomicInteger mCount = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "Media Player Executor #" + mCount.getAndIncrement());
                    }
                });
        this.context = context;
        mPlayer = new MediaPlayer();
        mPlayer.setOnCompletionListener(this);
        mPlayer.setOnPreparedListener(this);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    private void setSpeakerphoneOn(boolean on) {

        if (mAudioManager == null) {
            return;
        }
        if (on) {
            mAudioManager.setMode(AudioManager.MODE_NORMAL);
            mAudioManager.setSpeakerphoneOn(true);
        } else {
            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
            mAudioManager.setSpeakerphoneOn(false);
        }
    }

    public void start(String audioFile, AudioPlayCallback callback) {
        mAudioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        this.callback = callback;
        if (TextUtils.isEmpty(audioFile)) {
            if (callback != null) {
                callback.onError("'audioFile' cannot be empty");
            }
            return;
        }
        synchronized (mLock) {
            mPlayer.reset();
            try {
                mPlayer.setDataSource(audioFile);
            } catch (IOException e) {
                if (callback != null) {
                    callback.onError(e.getMessage());
                }
                e.printStackTrace();
                return;
            }
            try {
                mPlayer.prepareAsync();
            } catch (Exception e) {
                if (callback != null) {
                    callback.onError(e.getMessage());
                }
                e.printStackTrace();
                return;
            }
            dataSource = audioFile;
            acquireWakeLock();
            mPlayState = PlayerState.PLAYER_STATE_STARTED;
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        synchronized (mLock) {
            mp.start();
            mPlayState = PlayerState.PLAYER_STATE_PLAYING;
            mainThread.execute(() -> {
                if (callback != null) {
                    callback.onStart(mPlayer.getDuration());
                }
            });
            playFuture = playThread.submit(this);
        }
    }

    public void stop() {
        synchronized (mLock) {
            mPlayer.stop();
            mPlayer.reset();
            onCompletion();
            if (callback != null) {
                callback.onStop();
            }
        }
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        synchronized (mLock) {
            mp.reset();
            onCompletion();
            mainThread.execute(() -> {
                if (callback != null) {
                    callback.onCompletion();
                }
            });
        }
    }

    private void onCompletion() {
        mAudioManager.abandonAudioFocus(null);
        if (playFuture != null && !playFuture.isDone()) {
            playFuture.cancel(true);
        }
        mPlayState = PlayerState.PLAYER_STATE_IDLE;
        setSpeakerphoneOn(true);
        releaseWakeLock();
    }

    public void release() {
        if (mPlayState == PlayerState.PLAYER_STATE_STARTED) {
            stop();
        }
        synchronized (mLock) {
            mPlayer.release();
            mPlayState = PlayerState.PLAYER_STATE_RELEASED;
        }
        if (!playThread.isShutdown()) {
            playThread.shutdownNow();
        }
    }

    private void acquireWakeLock() {
        if (mWakeLock == null) {
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            if (pm != null) {
                mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, this.getClass().getSimpleName());
            }
            if (null != mWakeLock) {
                mWakeLock.acquire(60 * 1000 /*1 minutes*/);
            }
        }
    }

    private void releaseWakeLock() {
        if (mWakeLock != null && mWakeLock.isHeld()) {
            try {
                mWakeLock.release();
            } catch (Exception e) {
                // ignoring this exception, probably wakeLock was already released
            }
        }
    }

    @Override
    public void run() {
        while (mPlayer != null && mPlayState == PlayerState.PLAYER_STATE_PLAYING) {
            final int progress = mPlayer.getCurrentPosition();
            mainThread.execute(() -> {
//                Logger.i("current play progress>>>" + progress);
                if (callback != null) {
                    callback.onPlaying(progress);
                }
            });
        }
    }

    public boolean isPlaying() {
        return mPlayer.isPlaying();
    }

    public String getDataSource() {
        return dataSource;
    }

    public enum PlayerState {
        /**
         *
         */
        PLAYER_STATE_IDLE,
        PLAYER_STATE_STARTED,
        PLAYER_STATE_PLAYING,
        PLAYER_STATE_RELEASED
    }

    public interface AudioPlayCallback {
        void onStart(int duration);

        void onPlaying(int progress);

        void onCompletion();

        void onStop();

        void onError(String error);
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}
